﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;

namespace ConfigGen.Core.TokenHandling
{
    /// <summary>
    /// Class that is responible for locating a specified delimiter in a character stream. Characters are pushed, one-by-one, into this instance using the 
    /// <see cref="Push"/> method. If a token is located, the <see cref="DelimiterLocated"/> event is raised.
    /// </summary>
    public class DelimiterLocator
    {
        #region DelimiterLocated event

        /// <summary>
        /// Raised when the delimiter supplied at construction time is encountered in the stream of characters being pushed into this instance.
        /// </summary>
        public event EventHandler<DelimiterLocatedEventArgs> DelimiterLocated;
        
        /// <summary>
        /// Raises the <see cref="DelimiterLocated"/> event.
        /// </summary>
        /// <param name="sender">Instance that raised the event.</param>
        /// <param name="args">Event arguments</param>
        private void OnDelimiterLocated(object sender, DelimiterLocatedEventArgs args)
        {
            var handler = DelimiterLocated;
            if (handler != null)
            {
                handler(sender, args);
            }
        }
        #endregion

        private readonly Queue<char> _peekQueue = new Queue<char>();
        private int _delimiterIndex;
        private readonly string _delimiter;

        /// <summary>
        /// Creates a new instance of the <see cref="DelimiterLocator"/> class.
        /// </summary>
        /// <param name="delimiter">The delimiter this instance is meant to locate.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="delimiter"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="delimiter"/> is zero length.</exception>
        public DelimiterLocator(string delimiter)
        {
            if (delimiter == null) throw new ArgumentNullException("delimiter");
            if (delimiter.Length == 0) throw new ArgumentException("delimiter cannot be zero length", "delimiter");
            _delimiter = delimiter;
        }

        /// <summary>
        /// Pushes the supplied character from the source text stream into this locator.
        /// </summary>
        /// <remarks>
        /// <para>If the character being pushed is definitely not part of a delimiter it is immediately echoed in the return value.</para>
        /// <para>If the character could mark the start (or subsequent part) of a delimiter, null will be returned, and the character will be buffered internally.
        /// If, on a subsequent push, the characters turn out not to be a delimiter, then the entire buffer is returned on the push. If the characters
        /// do indeed turn out to be a delimiter, the <see cref="DelimiterLocated"/> event is raised, and the buffer is discarded.</para>
        /// <para>For example, if the delimiter is "[%", then when a "[" is encountered, buffering will start and null will be returned. 
        /// If the 2nd character is a "%", the event will be raised and the buffer discarded. 
        /// If, however, the 2nd character was a "a", then the characters "[" and "a" would be returned when the "a" was pushed.</para> 
        /// </remarks>
        /// <param name="character">Character being pushed.</param>
        /// <returns>Contents of buffer, or null.</returns>
        public char[] Push(char character)
        {
            _peekQueue.Enqueue(character);
            if (character == _delimiter[_delimiterIndex++])
            {
                // currently processing something that, so far, looks like our delimiter.

                if (_delimiterIndex == _delimiter.Length)
                {
                    // this is a delimiter, and we've reached the end of it.
                    // Clear the queue, and raise our event.
                    _peekQueue.Clear();
                    _delimiterIndex = 0;
                    OnDelimiterLocated(this, new DelimiterLocatedEventArgs(_delimiter));
                }

                return null;
            }

            _delimiterIndex = 0;

            var ret = _peekQueue.ToArray();
            _peekQueue.Clear();
            return ret;
        }

        /// <summary>
        /// Gets the delimiter this instance is meant to locate.
        /// </summary>
        public string Delimiter
        {
            get { return _delimiter; }
        }
    }
}
